1. Introducción y Objetivos

1.1. Contexto del Problema

1.2. Objetivos del Estudio

1.3. Descripción del Dataset

2. Selección de variables y preprocesamiento de datos

El dataset original de Airbnb (listings.csv) cuenta con 79 variables y más de 8.000 observaciones. Gran parte de esta información consiste en metadatos técnicos (scrape_id), textos no estructurados (description, urls) o redundancias que introducen ruido en nuestro análisis.

Para abordar las tres preguntas de negocio planteadas (Segmentación por barrio, Predicción de Precio y Análisis Geoespacial), hemos ejecutado una selección estratégica de características (Feature Selection). Hemos reducido la dimensionalidad del dataset a 19 variables clave, agrupadas en cuatro dimensiones funcionales que explican la varianza del mercado:

A. Dimensiones Estructurales (“El Hardware”)

Variables necesarias para estandarizar la comparación entre alojamientos heterogéneos.

  • room_type, accommodates, bedrooms: Definen la capacidad real y el modelo de alojamiento (privado vs. compartido). Son los predictores base del precio.

  • bathrooms_text: Seleccionada para ingeniería de características. El número de baños es un indicador crítico de lujo/gama alta que a menudo se omite en análisis básicos.

  • amenities: Aunque es texto, la transformaremos para cuantificar el “valor añadido” (piscina, aire acondicionado) que justifica sobreprecios.

B. Dimensiones Económicas y de Gestión (“El Negocio”)

Variables que permiten perfilar el comportamiento del anfitrión (Profesional vs. Particular).

  • price: Variable objetivo principal. Requiere limpieza de caracteres ($, ,) para su tratamiento numérico.

  • availability_365: Proxy de dedicación. Distingue entre negocios de dedicación exclusiva y alquileres esporádicos.

  • calculated_host_listings_count e instant_bookable: Métricas de profesionalización. Un anfitrión con múltiples propiedades y reserva inmediata tiene un perfil de riesgo y rotación distinto.

C. Dimensiones de Reputación y Calidad (“El Valor Percibido”)

Cruciales para el Clustering y para explicar outliers de precio.

  • reviews_per_month: El mejor indicador de la demanda actual y la rotación del activo.

  • review_scores_rating, _cleanliness, _location: Variables de calidad subjetiva. Permiten detectar alojamientos “sobrevalorados” (caros pero con mala nota) o “joyas ocultas”.

D. Dimensiones Geoespaciales (“La Ubicación”)

Necesarias para el análisis continuo del espacio urbano.

  • latitude / longitude: Materia prima para el cálculo de distancia euclídea/haversine a la Giralda (Pregunta 3).

  • neighbourhood_cleansed: Para establecer la línea base de precios por zona administrativa (Pregunta 2).

library(tidyverse) 
library(corrplot)
listings <- read.csv("./csv/listings.csv")
str(listings)
'data.frame':   8215 obs. of  79 variables:
 $ id                                          : num  49287 108236 111140 159596 179629 ...
 $ listing_url                                 : chr  "https://www.airbnb.com/rooms/49287" "https://www.airbnb.com/rooms/108236" "https://www.airbnb.com/rooms/111140" "https://www.airbnb.com/rooms/159596" ...
 $ scrape_id                                   : num  2.03e+13 2.03e+13 2.03e+13 2.03e+13 2.03e+13 ...
 $ last_scraped                                : chr  "2025-09-30" "2025-09-30" "2025-09-30" "2025-09-30" ...
 $ source                                      : chr  "city scrape" "city scrape" "city scrape" "previous scrape" ...
 $ name                                        : chr  "BEAUTIFUL APARTMENT IN SEVILLE" "Sunny apt in heart of seville!!" "Quiet&historicenter&local experienc" "apto lujo 2 D en el Arenal (Sevilla)" ...
 $ description                                 : chr  "Nice apartment on the second floor of a beautiful house in the famous Alameda square in the old center of Sevil"| __truncated__ "Clean, quiet and cosy 2-bedroom apartment near Fine Arts Museum. Queen size beds, bathroom with a bathtub and s"| __truncated__ "Nice and cozy apartment next to the Plaza del Museo de Bellas Artes. It has 1 double bedroom with large built-i"| __truncated__ "" ...
 $ neighborhood_overview                       : chr  "The famous Plaza de Hercules has changed a lot since its restoration in 2004. Its a lively neighbourhood with m"| __truncated__ "The apartment is located in a prime, picturesque area of Old Seville, just a few minutes' walk from the Museo d"| __truncated__ "The apartment is located in an unbeatable area of the old town of Seville. Next to the Plaza del Museo de Bella"| __truncated__ "It is in the middle of Seville in the luxurious neighborhood of El Arenal" ...
 $ picture_url                                 : chr  "https://a0.muscache.com/pictures/7a7b22d8-5f7f-4205-8d0b-1aaa690ae9b5.jpg" "https://a0.muscache.com/pictures/796657/67ae197e_original.jpg" "https://a0.muscache.com/pictures/797042/0cfbd568_original.jpg" "https://a0.muscache.com/pictures/2021d001-7779-4213-b027-a23b7d27f0d3.jpg" ...
 $ host_id                                     : int  224697 560040 560040 629861 860055 860055 1022533 1188880 1330663 1391956 ...
 $ host_url                                    : chr  "https://www.airbnb.com/users/show/224697" "https://www.airbnb.com/users/show/560040" "https://www.airbnb.com/users/show/560040" "https://www.airbnb.com/users/show/629861" ...
 $ host_name                                   : chr  "Walter" "José Luis" "José Luis" "Alvaro" ...
 $ host_since                                  : chr  "2010-09-05" "2011-05-05" "2011-05-05" "2011-05-26" ...
 $ host_location                               : chr  "Sevilla, Spain" "Seville, Spain" "Seville, Spain" "Sevilla, Spain" ...
 $ host_about                                  : chr  "Born in Holland and moved to Sevilla in 1997\nI am the owner of a computer shop and living together with my spa"| __truncated__ "Hello!\n\nI'm a former electrical engineer. Nowadays, I like travelling, being with friends, meeting new people"| __truncated__ "Hello!\n\nI'm a former electrical engineer. Nowadays, I like travelling, being with friends, meeting new people"| __truncated__ "ME GUSTA VIAJAR PPALMENTE EUROPA CENTRAL, TB USA, CARIBE, AUSTRALIA\nME GUSTA MUCHO INTERNET" ...
 $ host_response_time                          : chr  "within an hour" "within an hour" "within an hour" "a few days or more" ...
 $ host_response_rate                          : chr  "100%" "100%" "100%" "0%" ...
 $ host_acceptance_rate                        : chr  "100%" "92%" "92%" "0%" ...
 $ host_is_superhost                           : chr  "f" "t" "t" "f" ...
 $ host_thumbnail_url                          : chr  "https://a0.muscache.com/im/users/224697/profile_pic/1351593455/original.jpg?aki_policy=profile_small" "https://a0.muscache.com/im/pictures/user/User/original/2d53da87-eaa6-4189-b76b-a1d4e82239c5.jpeg?aki_policy=profile_small" "https://a0.muscache.com/im/pictures/user/User/original/2d53da87-eaa6-4189-b76b-a1d4e82239c5.jpeg?aki_policy=profile_small" "https://a0.muscache.com/im/users/629861/profile_pic/1337773141/original.jpg?aki_policy=profile_small" ...
 $ host_picture_url                            : chr  "https://a0.muscache.com/im/users/224697/profile_pic/1351593455/original.jpg?aki_policy=profile_x_medium" "https://a0.muscache.com/im/pictures/user/User/original/2d53da87-eaa6-4189-b76b-a1d4e82239c5.jpeg?aki_policy=profile_x_medium" "https://a0.muscache.com/im/pictures/user/User/original/2d53da87-eaa6-4189-b76b-a1d4e82239c5.jpeg?aki_policy=profile_x_medium" "https://a0.muscache.com/im/users/629861/profile_pic/1337773141/original.jpg?aki_policy=profile_x_medium" ...
 $ host_neighbourhood                          : chr  "Casco Antiguo" "Casco Antiguo" "Casco Antiguo" "Casco Antiguo" ...
 $ host_listings_count                         : int  1 4 4 2 24 24 8 2 2 7 ...
 $ host_total_listings_count                   : int  1 10 10 2 39 39 8 4 2 18 ...
 $ host_verifications                          : chr  "['email', 'phone', 'work_email']" "['email', 'phone']" "['email', 'phone']" "['email', 'phone']" ...
 $ host_has_profile_pic                        : chr  "t" "t" "t" "t" ...
 $ host_identity_verified                      : chr  "t" "t" "t" "t" ...
 $ neighbourhood                               : chr  "Seville, AL, Spain" "Seville, Andalusia, Spain" "Seville, Andalusia, Spain" "Seville, Andalucía, Spain" ...
 $ neighbourhood_cleansed                      : chr  "San Lorenzo" "San Vicente" "San Vicente" "Arenal" ...
 $ neighbourhood_group_cleansed                : chr  "Casco Antiguo" "Casco Antiguo" "Casco Antiguo" "Casco Antiguo" ...
 $ latitude                                    : num  37.4 37.4 37.4 37.4 37.4 ...
 $ longitude                                   : num  -6 -6 -6 -6 -5.99 ...
 $ property_type                               : chr  "Entire rental unit" "Entire rental unit" "Entire condo" "Entire rental unit" ...
 $ room_type                                   : chr  "Entire home/apt" "Entire home/apt" "Entire home/apt" "Entire home/apt" ...
 $ accommodates                                : int  3 5 4 4 5 7 2 1 2 6 ...
 $ bathrooms                                   : num  1 1 1 NA 1 NA 1 1.5 1 1 ...
 $ bathrooms_text                              : chr  "1 bath" "1 bath" "1 bath" "1 bath" ...
 $ bedrooms                                    : int  1 2 1 2 3 3 1 1 1 2 ...
 $ beds                                        : int  1 3 2 NA 4 NA 1 1 1 4 ...
 $ amenities                                   : chr  "[\"Bed linens\", \"Air conditioning\", \"Stove\", \"Dedicated workspace\", \"Shampoo\", \"Dishes and silverware"| __truncated__ "[\"Bed linens\", \"Lockbox\", \"Yamaha sound system with aux\", \"Carbon monoxide alarm\", \"Stainless steel ov"| __truncated__ "[\"Bed linens\", \"Lockbox\", \"Safe\", \"Dedicated workspace\", \"Shampoo\", \"Dishes and silverware\", \"Baki"| __truncated__ "[\"Bed linens\", \"Air conditioning\", \"Smoking allowed\", \"Dedicated workspace\", \"Dishes and silverware\","| __truncated__ ...
 $ price                                       : chr  "$93.00" "$120.00" "$89.00" "" ...
 $ minimum_nights                              : int  3 2 2 64 3 3 3 1 3 2 ...
 $ maximum_nights                              : int  1125 1125 1125 330 1125 365 30 7 365 365 ...
 $ minimum_minimum_nights                      : int  3 2 2 64 2 3 2 1 3 2 ...
 $ maximum_minimum_nights                      : int  3 2 2 64 3 3 3 3 3 3 ...
 $ minimum_maximum_nights                      : int  1125 1125 1125 330 1125 20 1125 7 365 365 ...
 $ maximum_maximum_nights                      : int  1125 1125 1125 330 1125 1125 1125 7 365 365 ...
 $ minimum_nights_avg_ntm                      : num  3 2 2 64 3 3 3 1.1 3 2 ...
 $ maximum_nights_avg_ntm                      : num  1125 1125 1125 330 1125 ...
 $ calendar_updated                            : logi  NA NA NA NA NA NA ...
 $ has_availability                            : chr  "t" "t" "t" "t" ...
 $ availability_30                             : int  5 5 2 0 7 8 8 3 11 4 ...
 $ availability_60                             : int  18 35 19 30 22 32 29 32 38 30 ...
 $ availability_90                             : int  41 65 38 60 37 45 56 62 63 32 ...
 $ availability_365                            : int  206 79 106 60 245 252 111 62 148 32 ...
 $ calendar_last_scraped                       : chr  "2025-09-30" "2025-09-30" "2025-09-30" "2025-09-30" ...
 $ number_of_reviews                           : int  40 222 63 1 205 120 508 238 331 459 ...
 $ number_of_reviews_ltm                       : int  1 25 11 0 3 7 53 41 18 34 ...
 $ number_of_reviews_l30d                      : int  1 3 0 0 0 0 6 1 1 2 ...
 $ availability_eoy                            : int  41 68 38 60 37 46 59 62 64 32 ...
 $ number_of_reviews_ly                        : int  0 16 10 0 13 10 56 32 35 38 ...
 $ estimated_occupancy_l365d                   : int  6 150 66 0 18 42 255 246 108 204 ...
 $ estimated_revenue_l365d                     : int  558 18000 5874 NA 2628 NA 21675 13284 8640 21420 ...
 $ first_review                                : chr  "2011-02-27" "2011-05-17" "2011-05-27" "2019-11-24" ...
 $ last_review                                 : chr  "2025-09-21" "2025-09-23" "2025-08-02" "2019-11-24" ...
 $ review_scores_rating                        : num  4.64 4.77 4.7 4 4.55 4.4 4.87 4.94 4.78 4.61 ...
 $ review_scores_accuracy                      : num  4.87 4.84 4.68 4 4.59 4.6 4.93 4.92 4.85 4.72 ...
 $ review_scores_cleanliness                   : num  4.97 4.81 4.69 5 4.77 4.73 4.95 4.96 4.68 4.72 ...
 $ review_scores_checkin                       : num  4.92 4.88 4.87 5 4.5 4.48 4.93 4.89 4.89 4.72 ...
 $ review_scores_communication                 : num  4.92 4.93 4.84 5 4.55 4.59 4.96 4.91 4.92 4.66 ...
 $ review_scores_location                      : num  4.92 4.86 4.82 5 4.93 4.17 4.94 4.91 4.93 4.88 ...
 $ review_scores_value                         : num  4.74 4.75 4.52 4 4.6 4.54 4.8 4.86 4.77 4.63 ...
 $ license                                     : chr  "VFT/SE/01116" "VFT/SE/05126" "VUT/SE/08197" "" ...
 $ instant_bookable                            : chr  "t" "f" "f" "f" ...
 $ calculated_host_listings_count              : int  1 4 4 2 24 24 1 2 2 5 ...
 $ calculated_host_listings_count_entire_homes : int  1 4 4 2 24 24 1 0 2 5 ...
 $ calculated_host_listings_count_private_rooms: int  0 0 0 0 0 0 0 2 0 0 ...
 $ calculated_host_listings_count_shared_rooms : int  0 0 0 0 0 0 0 0 0 0 ...
 $ reviews_per_month                           : num  0.23 1.27 0.36 0.01 1.23 0.7 3.78 1.41 1.96 2.76 ...

Antes de proceder al preprocesamiento, realizamos una inspección técnica del dataset crudo. De esta forma podemos analizar aspectos claves como:

  • Duplicidad

  • Missing values

  • Consistencia

# 1. ESTRUCTURA
print("--- Dimensiones del Dataset ---")
[1] "--- Dimensiones del Dataset ---"
dim(listings) # Filas x Columnas
[1] 8215   79
# 2. CHECK DE DUPLICADOS
duplicados_totales <- sum(duplicated(listings))
duplicados_id <- sum(duplicated(listings$id))

print(paste("Filas totalmente duplicadas:", duplicados_totales))
[1] "Filas totalmente duplicadas: 0"
print(paste("IDs repetidos (mismo piso scrapeado varias veces):", duplicados_id))
[1] "IDs repetidos (mismo piso scrapeado varias veces): 0"
# 3. MISSING VALUES
na_count <- colSums(is.na(listings))
na_percent <- (na_count / nrow(listings)) * 100

# Mostrar las 10 columnas con más nulos
print("--- Top 10 Columnas con más Nulos (%) ---")
[1] "--- Top 10 Columnas con más Nulos (%) ---"
print(sort(na_percent, decreasing = TRUE)[1:10])
           calendar_updated        review_scores_rating      review_scores_accuracy   review_scores_cleanliness 
                  100.00000                     8.82532                     8.82532                     8.82532 
      review_scores_checkin review_scores_communication      review_scores_location         review_scores_value 
                    8.82532                     8.82532                     8.82532                     8.82532 
          reviews_per_month     estimated_revenue_l365d 
                    8.82532                     7.71759 
# 4. Variables clave Room Type y Precios
# Ver si hay categorías raras antes de limpiar
print("--- Categorías únicas en Room Type ---")
[1] "--- Categorías únicas en Room Type ---"
print(table(listings$room_type))

Entire home/apt      Hotel room    Private room     Shared room 
           6993              21            1182              19 
# Ver formato del precio por si hay que limpiar
print("--- Ejemplo de Precios en crudo ---")
[1] "--- Ejemplo de Precios en crudo ---"
print(head(listings$price))
[1] "$93.00"  "$120.00" "$89.00"  ""        "$146.00" ""       

2.1. EDA inicial

En este punto hemos realizado un EDA pequeño, para hacernos una idea de donde partíamos, con qué tipo de datos tratamos, formato, valores nulos, incongruencias, en definitiva, qué sobra y qué nos parece interesante tratar y preprocesar en detalle.

library(ggplot2)
library(dplyr)

# GRÁFICO 1: EL "MONSTRUO" DE LOS PRECIOS
# Necesitamos convertir el precio a número "en sucio" solo para poder pintarlo
# (Sin modificar el dataset original todavía)
precios_sucios <- as.numeric(gsub("[\\$,]", "", listings$price))

# Creamos un dataframe temporal solo para este gráfico
df_plot_sucio <- data.frame(precio = precios_sucios)

ggplot(df_plot_sucio, aes(x = precio)) +
  geom_histogram(binwidth = 50, fill = "red", color = "black", alpha = 0.6) +
  theme_minimal() +
  labs(
    title = "Distribución Original de Precios (Sin Limpiar)",
    subtitle = "Extrema asimetría debido a outliers (pisos de 5.000€+)",
    x = "Precio (Crudo)",
    y = "Frecuencia"
  ) +
  annotate("text", x = 4000, y = 500, label = "Outliers Extremos\n(Distorsionan el modelo)", color = "red", fontface="bold")


# GRÁFICO 2: MAPA DE CALOR DE DATOS FALTANTES (NULOS)
# Calculamos el % de nulos por columna
na_summary <- data.frame(
  columna = names(listings),
  na_pct = colSums(is.na(listings)) / nrow(listings) * 100
) %>%
  filter(na_pct > 0) %>% # Solo mostramos las que tienen nulos
  arrange(desc(na_pct)) # Ordenamos de mayor a menor

# Pintamos solo las columnas problemáticas
ggplot(na_summary, aes(x = reorder(columna, na_pct), y = na_pct)) +
  geom_bar(stat = "identity", fill = "orange", width = 0.7) +
  coord_flip() + # Giramos para leer los nombres
  theme_minimal() +
  labs(
    title = "Porcentaje de Valores Nulos por Variable",
    subtitle = "Variables como 'calendar_updated' está vacía",
    x = "Variable",
    y = "% de Nulos"
  ) +
  geom_text(aes(label = round(na_pct, 1)), hjust = -0.2, size = 3)

Tras este pequeño análisis decidimos seleccionar las columnas que nos impactan. Así, podemos centrarnos en analizar profundamente solo las variables necesarias, y hacer el preprocesado detallado y centrado en nuestro caso concreto y no extendernos en algo que nos nos dará fruto para resolver nuestra hipótesis planteadas.

# 1. SELECCIÓN DE COLUMNAS
df_raw_selected <- listings %>%
  select(
    # Identificador
    id, 
    
    # Variables clave (Precios, Tipo y Estancia)
    price, 
    room_type, 
    accommodates, 
    minimum_nights, 
    
    # Variables estructurales
    bathrooms_text, 
    bedrooms, 
    amenities,
    
    # Ubicación
    neighbourhood_cleansed, 
    latitude, 
    longitude,
    
    # Actividad y reputación
    reviews_per_month, 
    number_of_reviews,
    review_scores_rating, 
    review_scores_cleanliness, 
    review_scores_location,
    
    # Gestión
    availability_365, 
    calculated_host_listings_count, 
    instant_bookable
  )

print(paste("Columnas seleccionadas:", ncol(df_raw_selected)))
[1] "Columnas seleccionadas: 19"

2.2. Preprocesado de Variables Críticas

df_final <- df_raw_selected %>%
  
  # --- 1. LIMPIEZA DE PRECIO ---
  mutate(
    price_clean = as.numeric(gsub("[\\$,]", "", price))
  ) %>%
  # Filtrar precios coherentes
  filter(price_clean > 10 & price_clean < 1000) %>%
  
  # --- 2. LIMPIEZA DE BAÑOS (VERSIÓN MEJORADA) ---
  mutate(
    # 2.1. Sacamos el NÚMERO (Cantidad)
    bathrooms_qty = as.numeric(str_extract(bathrooms_text, "\\d+(\\.\\d+)?")),
    bathrooms_qty = ifelse(is.na(bathrooms_qty) & str_detect(bathrooms_text, "Half-bath"), 0.5, bathrooms_qty),
    bathrooms_qty = ifelse(is.na(bathrooms_qty), 1, bathrooms_qty),
    
    # 2.2. Sacamos la PRIVACIDAD (Calidad)
    # Si detecta "shared", pone un 1. Si no, un 0.
    bath_shared = ifelse(str_detect(bathrooms_text, regex("shared", ignore_case = TRUE)), 1, 0)
  ) %>%
  
  # --- 3. INGENIERÍA DE AMENITIES ---
  mutate(
    amenities_count = str_count(amenities, ",") + 1,
    has_pool = as.integer(str_detect(amenities, regex("pool", ignore_case = TRUE))),
    has_ac = as.integer(str_detect(amenities, regex("air conditioning|ac", ignore_case = TRUE)))
  ) %>%
  
  # --- 4. TRATAMIENTO DE NULOS ---
  mutate(
    reviews_per_month = replace_na(reviews_per_month, 0),
    bedrooms = ifelse(is.na(bedrooms), median(bedrooms, na.rm = TRUE), bedrooms),
    review_scores_rating = ifelse(is.na(review_scores_rating), mean(review_scores_rating, na.rm = TRUE), review_scores_rating),
    review_scores_cleanliness = ifelse(is.na(review_scores_cleanliness), mean(review_scores_cleanliness, na.rm = TRUE), review_scores_cleanliness),
    review_scores_location = ifelse(is.na(review_scores_location), mean(review_scores_location, na.rm = TRUE), review_scores_location),
    instant_bookable_binary = ifelse(instant_bookable == "t", 1, 0),
    
    # LIMPIEZA DE MINIMUM_NIGHTS
    minimum_nights = ifelse(is.na(minimum_nights), 1, minimum_nights)
  ) %>%
  
  # --- 5. SELECCIÓN FINAL ---
  select(
    id, 
    price = price_clean, 
    room_type, 
    accommodates, 
    minimum_nights, 
    bedrooms, 
    bathrooms = bathrooms_qty, 
    bath_shared, 
    amenities_count, has_pool, has_ac, 
    neighbourhood_cleansed, 
    latitude, longitude, 
    reviews_per_month, number_of_reviews, 
    review_scores_rating, review_scores_cleanliness, review_scores_location,
    availability_365, calculated_host_listings_count, 
    instant_bookable = instant_bookable_binary
  ) %>%

  # --- 6. CONVERSIÓN A FACTORES (LO NUEVO) ---
  # Convertimos texto y binarios a categorías para que R los entienda bien en los gráficos
  mutate(
    room_type = as.factor(room_type),
    neighbourhood_cleansed = as.factor(neighbourhood_cleansed)
  )

# Verificar estructura final
glimpse(df_final)
Rows: 7,484
Columns: 22
$ id                             <dbl> 49287, 108236, 111140, 179629, 207702, 227905, 253430, 266016, 298673, 315971…
$ price                          <dbl> 93, 120, 89, 146, 85, 54, 80, 105, 127, 49, 103, 104, 132, 63, 116, 89, 80, 4…
$ room_type                      <fct> Entire home/apt, Entire home/apt, Entire home/apt, Entire home/apt, Entire ho…
$ accommodates                   <int> 3, 5, 4, 5, 2, 1, 2, 6, 2, 1, 4, 2, 4, 2, 4, 4, 4, 5, 2, 10, 3, 4, 4, 4, 2, 4…
$ minimum_nights                 <int> 3, 2, 2, 3, 3, 1, 3, 2, 2, 3, 1, 3, 2, 2, 1, 3, 3, 2, 3, 2, 3, 4, 1, 1, 15, 1…
$ bedrooms                       <dbl> 1, 2, 1, 3, 1, 1, 1, 2, 1, 1, 2, 1, 2, 1, 1, 1, 2, 3, 0, 3, 1, 1, 2, 1, 1, 1,…
$ bathrooms                      <dbl> 1.0, 1.0, 1.0, 1.0, 1.0, 1.5, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.5, 1.0, 1.0, 1.…
$ bath_shared                    <dbl> 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,…
$ amenities_count                <dbl> 50, 63, 53, 29, 37, 25, 35, 28, 51, 39, 33, 46, 42, 20, 33, 37, 45, 23, 29, 2…
$ has_pool                       <int> 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,…
$ has_ac                         <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,…
$ neighbourhood_cleansed         <fct> "San Lorenzo", "San Vicente", "San Vicente", "Santa Cruz", "Museo", "Museo", …
$ latitude                       <dbl> 37.39898, 37.39686, 37.39592, 37.38731, 37.38945, 37.39299, 37.39107, 37.3811…
$ longitude                      <dbl> -5.995330, -5.999127, -5.999317, -5.990950, -5.998890, -5.999160, -5.992610, …
$ reviews_per_month              <dbl> 0.23, 1.27, 0.36, 1.23, 3.78, 1.41, 1.96, 2.76, 3.71, 3.14, 2.88, 4.19, 0.75,…
$ number_of_reviews              <int> 40, 222, 63, 205, 508, 238, 331, 459, 611, 519, 474, 686, 58, 932, 417, 198, …
$ review_scores_rating           <dbl> 4.64, 4.77, 4.70, 4.55, 4.87, 4.94, 4.78, 4.61, 4.87, 4.75, 4.75, 4.84, 4.60,…
$ review_scores_cleanliness      <dbl> 4.97, 4.81, 4.69, 4.77, 4.95, 4.96, 4.68, 4.72, 4.91, 4.90, 4.89, 4.80, 4.64,…
$ review_scores_location         <dbl> 4.92, 4.86, 4.82, 4.93, 4.94, 4.91, 4.93, 4.88, 4.96, 4.84, 4.64, 4.98, 4.83,…
$ availability_365               <int> 206, 79, 106, 245, 111, 62, 148, 32, 120, 60, 68, 227, 60, 191, 276, 12, 252,…
$ calculated_host_listings_count <int> 1, 4, 4, 24, 1, 2, 2, 5, 205, 2, 1, 1, 6, 2, 4, 1, 1, 1, 4, 9, 1, 15, 1, 15, …
$ instant_bookable               <dbl> 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1,…
summary(df_final$minimum_nights)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
   1.00    1.00    2.00    2.96    2.00  360.00 

Para garantizar la robustez de los modelos posteriores (Clustering y Regresión), hemos implementado una transformación sobre el dataset original (df_raw_selected). Las operaciones realizadas son las siguientes:

1. Limpieza y Filtrado de la Variable Objetivo (Price) La variable precio original contenía caracteres no numéricos ($ y ,). Se ha convertido a tipo numérico y posterior filtrado.

  • Decisión: Se han eliminado registros con precios inferiores a 10€ (errores de carga o precios ínfimos) y superiores a 1.000€ (outliers extremos/mansiones).

    Esto reduce la asimetría de la distribución y evita que los valores extremos distorsionen la media y los centroides en el análisis de K-Means.

2. Extracción de Información Estructural (Bathrooms) La columna bathrooms_text presentaba información no estructurada (ej. “1.5 baths”). Hemos realizado las siguientes acciones:

  • Extraído el valor numérico.

  • Hemos imputado el valor 0.5 para los casos etiquetados como “Half-bath”.

  • Hemos asumido un valor estándar de 1 baño para los valores nulos restantes.

3. Características de Servicios (Amenities) La columna amenities es una lista de texto JSON compleja, muy interesante porque podemos recoger qué características “deluxe” tienen los pisos. Para ello, hemos creado tres nuevas variables sintéticas y asi cuantificar el valor añadido:

  • amenities_count: Conteo del número total de servicios ofrecidos, como indicador general del nivel de equipamiento.

  • has_pool y has_ac: Variables binarias (1/0) creadas mediante búsqueda de patrones de texto. Consideramos que en un sitio como Sevilla, la piscina y el aire acondicionado son críticos para el precio (Pregunta 2).

4. Estrategia seguida para la Imputación de Valores Nulos Para no perder observaciones valiosas, hemos aplicado una estrategia de imputación diferente, según cada variable:

  • Actividad (reviews_per_month): Los nulos se imputan con 0, interpretando la ausencia de dato como actividad nula.

  • Estructural (bedrooms): Se imputa con la mediana, al ser una medida más robusta frente a valores atípicos en variables de conteo entero.

  • Calidad (review_scores): Se imputa con la media global para mantener la neutralidad de la observación sin penalizarla artificialmente.

  • Noches minimas (minimum_nights): Se imputan los nulos con 1, asumiendo la estancia mínima estándar.

5. Selección y Renombrado Final : hemos generado el dataset df_final descartando las variables intermedias sucias (como bathrooms_text o el price original) y reteniendo únicamente las 19 variables limpias y transformadas que necesitaremos para trabajar los tres modelos del proyecto.

head(df_final)

2.3. Filtrado de Inconsistencias

# --- 2.3. FILTRADO DE INCONSISTENCIAS Y OUTLIERS ---
# Partimos de df_final que ya tiene las variables limpias (baños, amenities, etc.)

df_eda <- df_final %>%
  # A. Filtro de Precio: Ajustamos a tu rango (10€ - 1.000€)
  filter(price > 10 & price < 1000) %>%
  
  # B. Filtro Geoespacial: Seguridad por si falló algo
  filter(!is.na(latitude) & !is.na(longitude)) %>%
  
  # C. Filtro de Estancias: Eliminamos alquileres de año completo (posibles errores)
  filter(minimum_nights < 365)

# Ver cuántas filas nos quedan vs las originales
print(paste("Observaciones originales:", nrow(df_final)))
[1] "Observaciones originales: 7484"
print(paste("Observaciones para análisis (df_eda):", nrow(df_eda)))
[1] "Observaciones para análisis (df_eda): 7484"

3. Análisis Exploratorio de Datos

3.1. Análisis Univariante

3.1.1 Distribución del precio

# 3.1.1 Distribución del Precio
ggplot(df_eda, aes(x = price)) +
  geom_histogram(binwidth = 10, fill = "steelblue", color = "white") +
  theme_minimal() +
  labs(title = "Distribución de Precios (Filtrado 10€ - 1000€)", 
       subtitle = "La mayoría del mercado se concentra bajo los 200€",
       x = "Precio (€)", y = "Frecuencia")

3.1.2 Conteo del tipo de habitacion

# 3.1.2 Conteo por Tipo de Habitación
ggplot(df_eda, aes(x = room_type, fill = room_type)) +
  geom_bar() +
  geom_text(stat='count', aes(label=..count..), vjust=-0.5) +
  theme_minimal() +
  scale_fill_brewer(palette = "Pastel1") +
  labs(title = "Oferta por Tipo de Alojamiento") +
  theme(legend.position = "none")

3.1.3 Boxplot de noches minimas

# 3.1.3 (NUEVO) Distribución de Servicios (Amenities Count)
# Importante para justificar que hay pisos "pelados" y pisos "equipados"
ggplot(df_eda, aes(x = amenities_count)) +
  geom_histogram(binwidth = 2, fill = "purple", alpha = 0.7) +
  theme_minimal() +
  labs(title = "Distribución de Equipamiento (Amenities)", 
       x = "Cantidad de Servicios", y = "Nº Alojamientos")

NA
NA

3.2. Análisis Bivariante

3.2.1 Matriz de Correlación

Aquí detectamos la Multicolinealidad

# 3.2.1 Matriz de Correlación (Usando las nuevas variables numéricas)
library(corrplot)

# Seleccionamos las numéricas clave del nuevo dataset
nums <- df_eda %>% 
  select(price, accommodates, bedrooms, bathrooms, # Tu variable limpia
         amenities_count, minimum_nights,          # Tus variables nuevas
         number_of_reviews, reviews_per_month, 
         review_scores_rating) %>%
  na.omit()

M <- cor(nums)

corrplot(M, method = "color", type = "upper", 
         addCoef.col = "black", number.cex = 0.7, # Tamaño letra números
         tl.col = "black", tl.srt = 45, 
         diag = FALSE,
         title = "Matriz de Correlación (Variables Finales)", 
         mar = c(0,0,1,0)) # Ajuste de margen para el título

NA
NA

3.2.2 Impacto del Baño Compartido

# 3.2.2 Impacto del Baño Compartido
# Esto valida visualmente si 'bath_shared' afecta al precio
ggplot(df_eda, aes(x = as.factor(bath_shared), y = price, fill = as.factor(bath_shared))) +
  geom_boxplot(alpha = 0.7) +
  scale_x_discrete(labels = c("0" = "Privado", "1" = "Compartido")) +
  scale_fill_manual(values = c("#00AFBB", "#FC4E07")) +
  theme_minimal() +
  ylim(0, 300) + # Zoom para ver mejor las cajas (ignorando mansiones)
  labs(title = "Impacto del Baño Compartido en el Precio", 
       x = "Tipo de Baño", y = "Precio (€)", fill = "")

NA
NA

3.2.3 Scatterplot Price vs Reviews

# 3.2.3 Precio vs Reviews (Rotación)
ggplot(df_eda, aes(x = reviews_per_month, y = price)) +
  geom_point(alpha = 0.3, color = "darkblue") +
  geom_smooth(method = "lm", color = "red", se = FALSE) +
  theme_minimal() +
  labs(title = "Relación Precio vs Rotación", x = "Reviews/mes", y = "Precio")

3.3. Análisis Geoespacial

3.3.1 Mapa de Precios con la Giralda

# 3.3.1 Mapa de Precios con la Giralda
ggplot(df_eda, aes(x = longitude, y = latitude, color = price)) +
  geom_point(size = 0.8, alpha = 0.6) +
  
  # AÑADIR LA GIRALDA - Punto de referencia
  annotate("point", x = -5.99238, y = 37.38614, color = "yellow", size = 3, shape = 17) + 
  annotate("text", x = -5.99238, y = 37.38614, label = "Giralda", vjust = -1.5, color = "yellow", fontface = "bold", size=3) +
  
  scale_color_viridis_c(option = "magma", direction = -1, limits = c(0, 300), oob = scales::squish) + 
  # limits=c(0,300) hace que los colores se centren en pisos normales, oob=squish pinta los caros del color máximo
  
  coord_quickmap() +
  theme_void() +
  labs(title = "Mapa de Precios Sevilla Centro", color = "Precio")

4. Análisis Grupal

4.1. Segmentación del Mercado

4.2. Determinantes del Precio

4.3. El “Efecto Giralda”

LS0tCnRpdGxlOiAiUiBOb3RlYm9vayAtIEFpcmJuYiBTZXZpbGxhIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgojIDEuIEludHJvZHVjY2nDs24geSBPYmpldGl2b3MKCiMjIDEuMS4gQ29udGV4dG8gZGVsIFByb2JsZW1hCgojIyAxLjIuIE9iamV0aXZvcyBkZWwgRXN0dWRpbwoKIyMgMS4zLiBEZXNjcmlwY2nDs24gZGVsIERhdGFzZXQKCiMgMi4gU2VsZWNjacOzbiBkZSB2YXJpYWJsZXMgeSBwcmVwcm9jZXNhbWllbnRvIGRlIGRhdG9zCgpFbCBkYXRhc2V0IG9yaWdpbmFsIGRlIEFpcmJuYiAoYGxpc3RpbmdzLmNzdmApIGN1ZW50YSBjb24gKio3OSB2YXJpYWJsZXMqKiB5IG3DoXMgZGUgOC4wMDAgb2JzZXJ2YWNpb25lcy4gR3JhbiBwYXJ0ZSBkZSBlc3RhIGluZm9ybWFjacOzbiBjb25zaXN0ZSBlbiBtZXRhZGF0b3MgdMOpY25pY29zIChgc2NyYXBlX2lkYCksIHRleHRvcyBubyBlc3RydWN0dXJhZG9zIChgZGVzY3JpcHRpb25gLCBgdXJsc2ApIG8gcmVkdW5kYW5jaWFzIHF1ZSBpbnRyb2R1Y2VuIHJ1aWRvIGVuIG51ZXN0cm8gYW7DoWxpc2lzLgoKUGFyYSBhYm9yZGFyIGxhcyB0cmVzIHByZWd1bnRhcyBkZSBuZWdvY2lvIHBsYW50ZWFkYXMgKFNlZ21lbnRhY2nDs24gcG9yIGJhcnJpbywgUHJlZGljY2nDs24gZGUgUHJlY2lvIHkgQW7DoWxpc2lzIEdlb2VzcGFjaWFsKSwgaGVtb3MgZWplY3V0YWRvIHVuYSAqKnNlbGVjY2nDs24gZXN0cmF0w6lnaWNhIGRlIGNhcmFjdGVyw61zdGljYXMgKEZlYXR1cmUgU2VsZWN0aW9uKSoqLiBIZW1vcyByZWR1Y2lkbyBsYSBkaW1lbnNpb25hbGlkYWQgZGVsIGRhdGFzZXQgYSAqKjE5IHZhcmlhYmxlcyBjbGF2ZSoqLCBhZ3J1cGFkYXMgZW4gY3VhdHJvIGRpbWVuc2lvbmVzIGZ1bmNpb25hbGVzIHF1ZSBleHBsaWNhbiBsYSB2YXJpYW56YSBkZWwgbWVyY2FkbzoKCiMjIyMgKipBLiBEaW1lbnNpb25lcyBFc3RydWN0dXJhbGVzICgiRWwgSGFyZHdhcmUiKSoqCgpWYXJpYWJsZXMgbmVjZXNhcmlhcyBwYXJhIGVzdGFuZGFyaXphciBsYSBjb21wYXJhY2nDs24gZW50cmUgYWxvamFtaWVudG9zIGhldGVyb2fDqW5lb3MuCgotICAgKipgcm9vbV90eXBlYCoqLCAqKmBhY2NvbW1vZGF0ZXNgKiosICoqYGJlZHJvb21zYCoqOiBEZWZpbmVuIGxhIGNhcGFjaWRhZCByZWFsIHkgZWwgbW9kZWxvIGRlIGFsb2phbWllbnRvIChwcml2YWRvIHZzLiBjb21wYXJ0aWRvKS4gU29uIGxvcyBwcmVkaWN0b3JlcyBiYXNlIGRlbCBwcmVjaW8uCgotICAgKipgYmF0aHJvb21zX3RleHRgKio6IFNlbGVjY2lvbmFkYSBwYXJhIGluZ2VuaWVyw61hIGRlIGNhcmFjdGVyw61zdGljYXMuIEVsIG7Dum1lcm8gZGUgYmHDsW9zIGVzIHVuIGluZGljYWRvciBjcsOtdGljbyBkZSBsdWpvL2dhbWEgYWx0YSBxdWUgYSBtZW51ZG8gc2Ugb21pdGUgZW4gYW7DoWxpc2lzIGLDoXNpY29zLgoKLSAgICoqYGFtZW5pdGllc2AqKjogQXVucXVlIGVzIHRleHRvLCBsYSB0cmFuc2Zvcm1hcmVtb3MgcGFyYSBjdWFudGlmaWNhciBlbCAidmFsb3IgYcOxYWRpZG8iIChwaXNjaW5hLCBhaXJlIGFjb25kaWNpb25hZG8pIHF1ZSBqdXN0aWZpY2Egc29icmVwcmVjaW9zLgoKIyMjIyAqKkIuIERpbWVuc2lvbmVzIEVjb27Ds21pY2FzIHkgZGUgR2VzdGnDs24gKCJFbCBOZWdvY2lvIikqKgoKVmFyaWFibGVzIHF1ZSBwZXJtaXRlbiBwZXJmaWxhciBlbCBjb21wb3J0YW1pZW50byBkZWwgYW5maXRyacOzbiAoUHJvZmVzaW9uYWwgdnMuIFBhcnRpY3VsYXIpLgoKLSAgICoqYHByaWNlYCoqOiBWYXJpYWJsZSBvYmpldGl2byBwcmluY2lwYWwuIFJlcXVpZXJlIGxpbXBpZXphIGRlIGNhcmFjdGVyZXMgKGAkYCwgYCxgKSBwYXJhIHN1IHRyYXRhbWllbnRvIG51bcOpcmljby4KCi0gICAqKmBhdmFpbGFiaWxpdHlfMzY1YCoqOiBQcm94eSBkZSBkZWRpY2FjacOzbi4gRGlzdGluZ3VlIGVudHJlIG5lZ29jaW9zIGRlIGRlZGljYWNpw7NuIGV4Y2x1c2l2YSB5IGFscXVpbGVyZXMgZXNwb3LDoWRpY29zLgoKLSAgICoqYGNhbGN1bGF0ZWRfaG9zdF9saXN0aW5nc19jb3VudGAqKiBlICoqYGluc3RhbnRfYm9va2FibGVgKio6IE3DqXRyaWNhcyBkZSBwcm9mZXNpb25hbGl6YWNpw7NuLiBVbiBhbmZpdHJpw7NuIGNvbiBtw7psdGlwbGVzIHByb3BpZWRhZGVzIHkgcmVzZXJ2YSBpbm1lZGlhdGEgdGllbmUgdW4gcGVyZmlsIGRlIHJpZXNnbyB5IHJvdGFjacOzbiBkaXN0aW50by4KCiMjIyMgKipDLiBEaW1lbnNpb25lcyBkZSBSZXB1dGFjacOzbiB5IENhbGlkYWQgKCJFbCBWYWxvciBQZXJjaWJpZG8iKSoqCgpDcnVjaWFsZXMgcGFyYSBlbCBDbHVzdGVyaW5nIHkgcGFyYSBleHBsaWNhciBvdXRsaWVycyBkZSBwcmVjaW8uCgotICAgKipgcmV2aWV3c19wZXJfbW9udGhgKio6IEVsIG1lam9yIGluZGljYWRvciBkZSBsYSBkZW1hbmRhIGFjdHVhbCB5IGxhIHJvdGFjacOzbiBkZWwgYWN0aXZvLgoKLSAgICoqYHJldmlld19zY29yZXNfcmF0aW5nYCoqLCAqKmBfY2xlYW5saW5lc3NgKiosICoqYF9sb2NhdGlvbmAqKjogVmFyaWFibGVzIGRlIGNhbGlkYWQgc3ViamV0aXZhLiBQZXJtaXRlbiBkZXRlY3RhciBhbG9qYW1pZW50b3MgInNvYnJldmFsb3JhZG9zIiAoY2Fyb3MgcGVybyBjb24gbWFsYSBub3RhKSBvICJqb3lhcyBvY3VsdGFzIi4KCiMjIyMgKipELiBEaW1lbnNpb25lcyBHZW9lc3BhY2lhbGVzICgiTGEgVWJpY2FjacOzbiIpKioKCk5lY2VzYXJpYXMgcGFyYSBlbCBhbsOhbGlzaXMgY29udGludW8gZGVsIGVzcGFjaW8gdXJiYW5vLgoKLSAgICoqYGxhdGl0dWRlYCoqIC8gKipgbG9uZ2l0dWRlYCoqOiBNYXRlcmlhIHByaW1hIHBhcmEgZWwgY8OhbGN1bG8gZGUgZGlzdGFuY2lhIGV1Y2zDrWRlYS9oYXZlcnNpbmUgYSBsYSBHaXJhbGRhIChQcmVndW50YSAzKS4KCi0gICAqKmBuZWlnaGJvdXJob29kX2NsZWFuc2VkYCoqOiBQYXJhIGVzdGFibGVjZXIgbGEgbMOtbmVhIGJhc2UgZGUgcHJlY2lvcyBwb3Igem9uYSBhZG1pbmlzdHJhdGl2YSAoUHJlZ3VudGEgMikuCgpgYGB7cn0KbGlicmFyeSh0aWR5dmVyc2UpIApsaWJyYXJ5KGNvcnJwbG90KQpgYGAKCmBgYHtyfQpsaXN0aW5ncyA8LSByZWFkLmNzdigiLi9jc3YvbGlzdGluZ3MuY3N2IikKYGBgCgpgYGB7cn0Kc3RyKGxpc3RpbmdzKQpgYGAKCkFudGVzIGRlIHByb2NlZGVyIGFsIHByZXByb2Nlc2FtaWVudG8sIHJlYWxpemFtb3MgdW5hIGluc3BlY2Npw7NuIHTDqWNuaWNhIGRlbCBkYXRhc2V0IGNydWRvLiBEZSBlc3RhIGZvcm1hIHBvZGVtb3MgYW5hbGl6YXIgYXNwZWN0b3MgY2xhdmVzIGNvbW86CgotICAgRHVwbGljaWRhZAoKLSAgIE1pc3NpbmcgdmFsdWVzCgotICAgQ29uc2lzdGVuY2lhCgpgYGB7cn0KIyAxLiBFU1RSVUNUVVJBCnByaW50KCItLS0gRGltZW5zaW9uZXMgZGVsIERhdGFzZXQgLS0tIikKZGltKGxpc3RpbmdzKSAjIEZpbGFzIHggQ29sdW1uYXMKCiMgMi4gQ0hFQ0sgREUgRFVQTElDQURPUwpkdXBsaWNhZG9zX3RvdGFsZXMgPC0gc3VtKGR1cGxpY2F0ZWQobGlzdGluZ3MpKQpkdXBsaWNhZG9zX2lkIDwtIHN1bShkdXBsaWNhdGVkKGxpc3RpbmdzJGlkKSkKCnByaW50KHBhc3RlKCJGaWxhcyB0b3RhbG1lbnRlIGR1cGxpY2FkYXM6IiwgZHVwbGljYWRvc190b3RhbGVzKSkKcHJpbnQocGFzdGUoIklEcyByZXBldGlkb3MgKG1pc21vIHBpc28gc2NyYXBlYWRvIHZhcmlhcyB2ZWNlcyk6IiwgZHVwbGljYWRvc19pZCkpCgojIDMuIE1JU1NJTkcgVkFMVUVTCm5hX2NvdW50IDwtIGNvbFN1bXMoaXMubmEobGlzdGluZ3MpKQpuYV9wZXJjZW50IDwtIChuYV9jb3VudCAvIG5yb3cobGlzdGluZ3MpKSAqIDEwMAoKIyBNb3N0cmFyIGxhcyAxMCBjb2x1bW5hcyBjb24gbcOhcyBudWxvcwpwcmludCgiLS0tIFRvcCAxMCBDb2x1bW5hcyBjb24gbcOhcyBOdWxvcyAoJSkgLS0tIikKcHJpbnQoc29ydChuYV9wZXJjZW50LCBkZWNyZWFzaW5nID0gVFJVRSlbMToxMF0pCgojIDQuIFZhcmlhYmxlcyBjbGF2ZSBSb29tIFR5cGUgeSBQcmVjaW9zCiMgVmVyIHNpIGhheSBjYXRlZ29yw61hcyByYXJhcyBhbnRlcyBkZSBsaW1waWFyCnByaW50KCItLS0gQ2F0ZWdvcsOtYXMgw7puaWNhcyBlbiBSb29tIFR5cGUgLS0tIikKcHJpbnQodGFibGUobGlzdGluZ3Mkcm9vbV90eXBlKSkKCiMgVmVyIGZvcm1hdG8gZGVsIHByZWNpbyBwb3Igc2kgaGF5IHF1ZSBsaW1waWFyCnByaW50KCItLS0gRWplbXBsbyBkZSBQcmVjaW9zIGVuIGNydWRvIC0tLSIpCnByaW50KGhlYWQobGlzdGluZ3MkcHJpY2UpKQpgYGAKCiMjIDIuMS4gRURBIGluaWNpYWwKCkVuIGVzdGUgcHVudG8gaGVtb3MgcmVhbGl6YWRvIHVuIEVEQSBwZXF1ZcOxbywgcGFyYSBoYWNlcm5vcyB1bmEgaWRlYSBkZSBkb25kZSBwYXJ0w61hbW9zLCBjb24gcXXDqSB0aXBvIGRlIGRhdG9zIHRyYXRhbW9zLCBmb3JtYXRvLCB2YWxvcmVzIG51bG9zLCBpbmNvbmdydWVuY2lhcywgZW4gZGVmaW5pdGl2YSwgcXXDqSBzb2JyYSB5IHF1w6kgbm9zIHBhcmVjZSBpbnRlcmVzYW50ZSB0cmF0YXIgeSBwcmVwcm9jZXNhciBlbiBkZXRhbGxlLgoKYGBge3J9CmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeShkcGx5cikKCiMgR1LDgUZJQ08gMTogRUwgIk1PTlNUUlVPIiBERSBMT1MgUFJFQ0lPUwojIE5lY2VzaXRhbW9zIGNvbnZlcnRpciBlbCBwcmVjaW8gYSBuw7ptZXJvICJlbiBzdWNpbyIgc29sbyBwYXJhIHBvZGVyIHBpbnRhcmxvCiMgKFNpbiBtb2RpZmljYXIgZWwgZGF0YXNldCBvcmlnaW5hbCB0b2RhdsOtYSkKcHJlY2lvc19zdWNpb3MgPC0gYXMubnVtZXJpYyhnc3ViKCJbXFwkLF0iLCAiIiwgbGlzdGluZ3MkcHJpY2UpKQoKIyBDcmVhbW9zIHVuIGRhdGFmcmFtZSB0ZW1wb3JhbCBzb2xvIHBhcmEgZXN0ZSBncsOhZmljbwpkZl9wbG90X3N1Y2lvIDwtIGRhdGEuZnJhbWUocHJlY2lvID0gcHJlY2lvc19zdWNpb3MpCgpnZ3Bsb3QoZGZfcGxvdF9zdWNpbywgYWVzKHggPSBwcmVjaW8pKSArCiAgZ2VvbV9oaXN0b2dyYW0oYmlud2lkdGggPSA1MCwgZmlsbCA9ICJyZWQiLCBjb2xvciA9ICJibGFjayIsIGFscGhhID0gMC42KSArCiAgdGhlbWVfbWluaW1hbCgpICsKICBsYWJzKAogICAgdGl0bGUgPSAiRGlzdHJpYnVjacOzbiBPcmlnaW5hbCBkZSBQcmVjaW9zIChTaW4gTGltcGlhcikiLAogICAgc3VidGl0bGUgPSAiRXh0cmVtYSBhc2ltZXRyw61hIGRlYmlkbyBhIG91dGxpZXJzIChwaXNvcyBkZSA1LjAwMOKCrCspIiwKICAgIHggPSAiUHJlY2lvIChDcnVkbykiLAogICAgeSA9ICJGcmVjdWVuY2lhIgogICkgKwogIGFubm90YXRlKCJ0ZXh0IiwgeCA9IDQwMDAsIHkgPSA1MDAsIGxhYmVsID0gIk91dGxpZXJzIEV4dHJlbW9zXG4oRGlzdG9yc2lvbmFuIGVsIG1vZGVsbykiLCBjb2xvciA9ICJyZWQiLCBmb250ZmFjZT0iYm9sZCIpCgojIEdSw4FGSUNPIDI6IE1BUEEgREUgQ0FMT1IgREUgREFUT1MgRkFMVEFOVEVTIChOVUxPUykKIyBDYWxjdWxhbW9zIGVsICUgZGUgbnVsb3MgcG9yIGNvbHVtbmEKbmFfc3VtbWFyeSA8LSBkYXRhLmZyYW1lKAogIGNvbHVtbmEgPSBuYW1lcyhsaXN0aW5ncyksCiAgbmFfcGN0ID0gY29sU3Vtcyhpcy5uYShsaXN0aW5ncykpIC8gbnJvdyhsaXN0aW5ncykgKiAxMDAKKSAlPiUKICBmaWx0ZXIobmFfcGN0ID4gMCkgJT4lICMgU29sbyBtb3N0cmFtb3MgbGFzIHF1ZSB0aWVuZW4gbnVsb3MKICBhcnJhbmdlKGRlc2MobmFfcGN0KSkgIyBPcmRlbmFtb3MgZGUgbWF5b3IgYSBtZW5vcgoKIyBQaW50YW1vcyBzb2xvIGxhcyBjb2x1bW5hcyBwcm9ibGVtw6F0aWNhcwpnZ3Bsb3QobmFfc3VtbWFyeSwgYWVzKHggPSByZW9yZGVyKGNvbHVtbmEsIG5hX3BjdCksIHkgPSBuYV9wY3QpKSArCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIGZpbGwgPSAib3JhbmdlIiwgd2lkdGggPSAwLjcpICsKICBjb29yZF9mbGlwKCkgKyAjIEdpcmFtb3MgcGFyYSBsZWVyIGxvcyBub21icmVzCiAgdGhlbWVfbWluaW1hbCgpICsKICBsYWJzKAogICAgdGl0bGUgPSAiUG9yY2VudGFqZSBkZSBWYWxvcmVzIE51bG9zIHBvciBWYXJpYWJsZSIsCiAgICBzdWJ0aXRsZSA9ICJWYXJpYWJsZXMgY29tbyAnY2FsZW5kYXJfdXBkYXRlZCcgZXN0w6EgdmFjw61hIiwKICAgIHggPSAiVmFyaWFibGUiLAogICAgeSA9ICIlIGRlIE51bG9zIgogICkgKwogIGdlb21fdGV4dChhZXMobGFiZWwgPSByb3VuZChuYV9wY3QsIDEpKSwgaGp1c3QgPSAtMC4yLCBzaXplID0gMykKYGBgCgpUcmFzIGVzdGUgcGVxdWXDsW8gYW7DoWxpc2lzIGRlY2lkaW1vcyBzZWxlY2Npb25hciBsYXMgY29sdW1uYXMgcXVlIG5vcyBpbXBhY3Rhbi4gQXPDrSwgcG9kZW1vcyBjZW50cmFybm9zIGVuIGFuYWxpemFyIHByb2Z1bmRhbWVudGUgc29sbyBsYXMgdmFyaWFibGVzIG5lY2VzYXJpYXMsIHkgaGFjZXIgZWwgcHJlcHJvY2VzYWRvIGRldGFsbGFkbyB5IGNlbnRyYWRvIGVuIG51ZXN0cm8gY2FzbyBjb25jcmV0byB5IG5vIGV4dGVuZGVybm9zIGVuIGFsZ28gcXVlIG5vcyBub3MgZGFyw6EgZnJ1dG8gcGFyYSByZXNvbHZlciBudWVzdHJhIGhpcMOzdGVzaXMgcGxhbnRlYWRhcy4KCmBgYHtyfQojIDEuIFNFTEVDQ0nDk04gREUgQ09MVU1OQVMKZGZfcmF3X3NlbGVjdGVkIDwtIGxpc3RpbmdzICU+JQogIHNlbGVjdCgKICAgICMgSWRlbnRpZmljYWRvcgogICAgaWQsIAogICAgCiAgICAjIFZhcmlhYmxlcyBjbGF2ZSAoUHJlY2lvcywgVGlwbyB5IEVzdGFuY2lhKQogICAgcHJpY2UsIAogICAgcm9vbV90eXBlLCAKICAgIGFjY29tbW9kYXRlcywgCiAgICBtaW5pbXVtX25pZ2h0cywgCiAgICAKICAgICMgVmFyaWFibGVzIGVzdHJ1Y3R1cmFsZXMKICAgIGJhdGhyb29tc190ZXh0LCAKICAgIGJlZHJvb21zLCAKICAgIGFtZW5pdGllcywKICAgIAogICAgIyBVYmljYWNpw7NuCiAgICBuZWlnaGJvdXJob29kX2NsZWFuc2VkLCAKICAgIGxhdGl0dWRlLCAKICAgIGxvbmdpdHVkZSwKICAgIAogICAgIyBBY3RpdmlkYWQgeSByZXB1dGFjacOzbgogICAgcmV2aWV3c19wZXJfbW9udGgsIAogICAgbnVtYmVyX29mX3Jldmlld3MsCiAgICByZXZpZXdfc2NvcmVzX3JhdGluZywgCiAgICByZXZpZXdfc2NvcmVzX2NsZWFubGluZXNzLCAKICAgIHJldmlld19zY29yZXNfbG9jYXRpb24sCiAgICAKICAgICMgR2VzdGnDs24KICAgIGF2YWlsYWJpbGl0eV8zNjUsIAogICAgY2FsY3VsYXRlZF9ob3N0X2xpc3RpbmdzX2NvdW50LCAKICAgIGluc3RhbnRfYm9va2FibGUKICApCgpwcmludChwYXN0ZSgiQ29sdW1uYXMgc2VsZWNjaW9uYWRhczoiLCBuY29sKGRmX3Jhd19zZWxlY3RlZCkpKQpgYGAKCiMjIDIuMi4gUHJlcHJvY2VzYWRvIGRlIFZhcmlhYmxlcyBDcsOtdGljYXMKCmBgYHtyfQpkZl9maW5hbCA8LSBkZl9yYXdfc2VsZWN0ZWQgJT4lCiAgCiAgIyAtLS0gMS4gTElNUElFWkEgREUgUFJFQ0lPIC0tLQogIG11dGF0ZSgKICAgIHByaWNlX2NsZWFuID0gYXMubnVtZXJpYyhnc3ViKCJbXFwkLF0iLCAiIiwgcHJpY2UpKQogICkgJT4lCiAgIyBGaWx0cmFyIHByZWNpb3MgY29oZXJlbnRlcwogIGZpbHRlcihwcmljZV9jbGVhbiA+IDEwICYgcHJpY2VfY2xlYW4gPCAxMDAwKSAlPiUKICAKICAjIC0tLSAyLiBMSU1QSUVaQSBERSBCQcORT1MgKFZFUlNJw5NOIE1FSk9SQURBKSAtLS0KICBtdXRhdGUoCiAgICAjIDIuMS4gU2FjYW1vcyBlbCBOw5pNRVJPIChDYW50aWRhZCkKICAgIGJhdGhyb29tc19xdHkgPSBhcy5udW1lcmljKHN0cl9leHRyYWN0KGJhdGhyb29tc190ZXh0LCAiXFxkKyhcXC5cXGQrKT8iKSksCiAgICBiYXRocm9vbXNfcXR5ID0gaWZlbHNlKGlzLm5hKGJhdGhyb29tc19xdHkpICYgc3RyX2RldGVjdChiYXRocm9vbXNfdGV4dCwgIkhhbGYtYmF0aCIpLCAwLjUsIGJhdGhyb29tc19xdHkpLAogICAgYmF0aHJvb21zX3F0eSA9IGlmZWxzZShpcy5uYShiYXRocm9vbXNfcXR5KSwgMSwgYmF0aHJvb21zX3F0eSksCiAgICAKICAgICMgMi4yLiBTYWNhbW9zIGxhIFBSSVZBQ0lEQUQgKENhbGlkYWQpCiAgICAjIFNpIGRldGVjdGEgInNoYXJlZCIsIHBvbmUgdW4gMS4gU2kgbm8sIHVuIDAuCiAgICBiYXRoX3NoYXJlZCA9IGlmZWxzZShzdHJfZGV0ZWN0KGJhdGhyb29tc190ZXh0LCByZWdleCgic2hhcmVkIiwgaWdub3JlX2Nhc2UgPSBUUlVFKSksIDEsIDApCiAgKSAlPiUKICAKICAjIC0tLSAzLiBJTkdFTklFUsONQSBERSBBTUVOSVRJRVMgLS0tCiAgbXV0YXRlKAogICAgYW1lbml0aWVzX2NvdW50ID0gc3RyX2NvdW50KGFtZW5pdGllcywgIiwiKSArIDEsCiAgICBoYXNfcG9vbCA9IGFzLmludGVnZXIoc3RyX2RldGVjdChhbWVuaXRpZXMsIHJlZ2V4KCJwb29sIiwgaWdub3JlX2Nhc2UgPSBUUlVFKSkpLAogICAgaGFzX2FjID0gYXMuaW50ZWdlcihzdHJfZGV0ZWN0KGFtZW5pdGllcywgcmVnZXgoImFpciBjb25kaXRpb25pbmd8YWMiLCBpZ25vcmVfY2FzZSA9IFRSVUUpKSkKICApICU+JQogIAogICMgLS0tIDQuIFRSQVRBTUlFTlRPIERFIE5VTE9TIC0tLQogIG11dGF0ZSgKICAgIHJldmlld3NfcGVyX21vbnRoID0gcmVwbGFjZV9uYShyZXZpZXdzX3Blcl9tb250aCwgMCksCiAgICBiZWRyb29tcyA9IGlmZWxzZShpcy5uYShiZWRyb29tcyksIG1lZGlhbihiZWRyb29tcywgbmEucm0gPSBUUlVFKSwgYmVkcm9vbXMpLAogICAgcmV2aWV3X3Njb3Jlc19yYXRpbmcgPSBpZmVsc2UoaXMubmEocmV2aWV3X3Njb3Jlc19yYXRpbmcpLCBtZWFuKHJldmlld19zY29yZXNfcmF0aW5nLCBuYS5ybSA9IFRSVUUpLCByZXZpZXdfc2NvcmVzX3JhdGluZyksCiAgICByZXZpZXdfc2NvcmVzX2NsZWFubGluZXNzID0gaWZlbHNlKGlzLm5hKHJldmlld19zY29yZXNfY2xlYW5saW5lc3MpLCBtZWFuKHJldmlld19zY29yZXNfY2xlYW5saW5lc3MsIG5hLnJtID0gVFJVRSksIHJldmlld19zY29yZXNfY2xlYW5saW5lc3MpLAogICAgcmV2aWV3X3Njb3Jlc19sb2NhdGlvbiA9IGlmZWxzZShpcy5uYShyZXZpZXdfc2NvcmVzX2xvY2F0aW9uKSwgbWVhbihyZXZpZXdfc2NvcmVzX2xvY2F0aW9uLCBuYS5ybSA9IFRSVUUpLCByZXZpZXdfc2NvcmVzX2xvY2F0aW9uKSwKICAgIGluc3RhbnRfYm9va2FibGVfYmluYXJ5ID0gaWZlbHNlKGluc3RhbnRfYm9va2FibGUgPT0gInQiLCAxLCAwKSwKICAgIAogICAgIyBMSU1QSUVaQSBERSBNSU5JTVVNX05JR0hUUwogICAgbWluaW11bV9uaWdodHMgPSBpZmVsc2UoaXMubmEobWluaW11bV9uaWdodHMpLCAxLCBtaW5pbXVtX25pZ2h0cykKICApICU+JQogIAogICMgLS0tIDUuIFNFTEVDQ0nDk04gRklOQUwgLS0tCiAgc2VsZWN0KAogICAgaWQsIAogICAgcHJpY2UgPSBwcmljZV9jbGVhbiwgCiAgICByb29tX3R5cGUsIAogICAgYWNjb21tb2RhdGVzLCAKICAgIG1pbmltdW1fbmlnaHRzLCAKICAgIGJlZHJvb21zLCAKICAgIGJhdGhyb29tcyA9IGJhdGhyb29tc19xdHksIAogICAgYmF0aF9zaGFyZWQsIAogICAgYW1lbml0aWVzX2NvdW50LCBoYXNfcG9vbCwgaGFzX2FjLCAKICAgIG5laWdoYm91cmhvb2RfY2xlYW5zZWQsIAogICAgbGF0aXR1ZGUsIGxvbmdpdHVkZSwgCiAgICByZXZpZXdzX3Blcl9tb250aCwgbnVtYmVyX29mX3Jldmlld3MsIAogICAgcmV2aWV3X3Njb3Jlc19yYXRpbmcsIHJldmlld19zY29yZXNfY2xlYW5saW5lc3MsIHJldmlld19zY29yZXNfbG9jYXRpb24sCiAgICBhdmFpbGFiaWxpdHlfMzY1LCBjYWxjdWxhdGVkX2hvc3RfbGlzdGluZ3NfY291bnQsIAogICAgaW5zdGFudF9ib29rYWJsZSA9IGluc3RhbnRfYm9va2FibGVfYmluYXJ5CiAgKSAlPiUKCiAgIyAtLS0gNi4gQ09OVkVSU0nDk04gQSBGQUNUT1JFUyAoTE8gTlVFVk8pIC0tLQogICMgQ29udmVydGltb3MgdGV4dG8geSBiaW5hcmlvcyBhIGNhdGVnb3LDrWFzIHBhcmEgcXVlIFIgbG9zIGVudGllbmRhIGJpZW4gZW4gbG9zIGdyw6FmaWNvcwogIG11dGF0ZSgKICAgIHJvb21fdHlwZSA9IGFzLmZhY3Rvcihyb29tX3R5cGUpLAogICAgbmVpZ2hib3VyaG9vZF9jbGVhbnNlZCA9IGFzLmZhY3RvcihuZWlnaGJvdXJob29kX2NsZWFuc2VkKQogICkKCiMgVmVyaWZpY2FyIGVzdHJ1Y3R1cmEgZmluYWwKZ2xpbXBzZShkZl9maW5hbCkKc3VtbWFyeShkZl9maW5hbCRtaW5pbXVtX25pZ2h0cykKYGBgCgpQYXJhIGdhcmFudGl6YXIgbGEgcm9idXN0ZXogZGUgbG9zIG1vZGVsb3MgcG9zdGVyaW9yZXMgKENsdXN0ZXJpbmcgeSBSZWdyZXNpw7NuKSwgaGVtb3MgaW1wbGVtZW50YWRvIHVuYSB0cmFuc2Zvcm1hY2nDs24gc29icmUgZWwgZGF0YXNldCBvcmlnaW5hbCAoYGRmX3Jhd19zZWxlY3RlZGApLiBMYXMgb3BlcmFjaW9uZXMgcmVhbGl6YWRhcyBzb24gbGFzIHNpZ3VpZW50ZXM6CgoqKjEuIExpbXBpZXphIHkgRmlsdHJhZG8gZGUgbGEgVmFyaWFibGUgT2JqZXRpdm8gKGBQcmljZWApKiogTGEgdmFyaWFibGUgcHJlY2lvIG9yaWdpbmFsIGNvbnRlbsOtYSBjYXJhY3RlcmVzIG5vIG51bcOpcmljb3MgKGAkYCB5IGAsYCkuIFNlIGhhIGNvbnZlcnRpZG8gYSB0aXBvIG51bcOpcmljbyB5IHBvc3RlcmlvciBmaWx0cmFkby4KCi0gICAqKkRlY2lzacOzbjoqKiBTZSBoYW4gZWxpbWluYWRvIHJlZ2lzdHJvcyBjb24gcHJlY2lvcyBpbmZlcmlvcmVzIGEgKioxMOKCrCoqIChlcnJvcmVzIGRlIGNhcmdhIG8gcHJlY2lvcyDDrW5maW1vcykgeSBzdXBlcmlvcmVzIGEgKioxLjAwMOKCrCoqIChvdXRsaWVycyBleHRyZW1vcy9tYW5zaW9uZXMpLlwKICAgIFwKICAgIEVzdG8gcmVkdWNlIGxhIGFzaW1ldHLDrWEgZGUgbGEgZGlzdHJpYnVjacOzbiB5IGV2aXRhIHF1ZSBsb3MgdmFsb3JlcyBleHRyZW1vcyBkaXN0b3JzaW9uZW4gbGEgbWVkaWEgeSBsb3MgY2VudHJvaWRlcyBlbiBlbCBhbsOhbGlzaXMgZGUgSy1NZWFucy4KCioqMi4gRXh0cmFjY2nDs24gZGUgSW5mb3JtYWNpw7NuIEVzdHJ1Y3R1cmFsIChgQmF0aHJvb21zYCkqKiBMYSBjb2x1bW5hIGBiYXRocm9vbXNfdGV4dGAgcHJlc2VudGFiYSBpbmZvcm1hY2nDs24gbm8gZXN0cnVjdHVyYWRhIChlai4gKiIxLjUgYmF0aHMiKikuIEhlbW9zIHJlYWxpemFkbyBsYXMgc2lndWllbnRlcyBhY2Npb25lczoKCi0gICBFeHRyYcOtZG8gZWwgdmFsb3IgbnVtw6lyaWNvLgoKLSAgIEhlbW9zIGltcHV0YWRvIGVsIHZhbG9yICoqMC41KiogcGFyYSBsb3MgY2Fzb3MgZXRpcXVldGFkb3MgY29tbyAqIkhhbGYtYmF0aCIqLgoKLSAgIEhlbW9zIGFzdW1pZG8gdW4gdmFsb3IgZXN0w6FuZGFyIGRlICoqMSoqIGJhw7FvIHBhcmEgbG9zIHZhbG9yZXMgbnVsb3MgcmVzdGFudGVzLgoKKiozLiBDYXJhY3RlcsOtc3RpY2FzIGRlIFNlcnZpY2lvcyAoYEFtZW5pdGllc2ApKiogTGEgY29sdW1uYSBgYW1lbml0aWVzYCBlcyB1bmEgbGlzdGEgZGUgdGV4dG8gSlNPTiBjb21wbGVqYSwgbXV5IGludGVyZXNhbnRlIHBvcnF1ZSBwb2RlbW9zIHJlY29nZXIgcXXDqSBjYXJhY3RlcsOtc3RpY2FzICJkZWx1eGUiIHRpZW5lbiBsb3MgcGlzb3MuIFBhcmEgZWxsbywgaGVtb3MgY3JlYWRvIHRyZXMgbnVldmFzIHZhcmlhYmxlcyBzaW50w6l0aWNhcyB5IGFzaSBjdWFudGlmaWNhciBlbCB2YWxvciBhw7FhZGlkbzoKCi0gICAqKmBhbWVuaXRpZXNfY291bnRgKio6IENvbnRlbyBkZWwgbsO6bWVybyB0b3RhbCBkZSBzZXJ2aWNpb3Mgb2ZyZWNpZG9zLCBjb21vIGluZGljYWRvciBnZW5lcmFsIGRlbCBuaXZlbCBkZSBlcXVpcGFtaWVudG8uCgotICAgKipgaGFzX3Bvb2xgKiogeSAqKmBoYXNfYWNgKio6IFZhcmlhYmxlcyBiaW5hcmlhcyAoMS8wKSBjcmVhZGFzIG1lZGlhbnRlIGLDunNxdWVkYSBkZSBwYXRyb25lcyBkZSB0ZXh0by4gQ29uc2lkZXJhbW9zIHF1ZSBlbiB1biBzaXRpbyBjb21vIFNldmlsbGEsIGxhIHBpc2NpbmEgeSBlbCBhaXJlIGFjb25kaWNpb25hZG8gc29uIGNyw610aWNvcyBwYXJhIGVsIHByZWNpbyAoUHJlZ3VudGEgMikuCgoqKjQuIEVzdHJhdGVnaWEgc2VndWlkYSBwYXJhIGxhIEltcHV0YWNpw7NuIGRlIFZhbG9yZXMgTnVsb3MqKiBQYXJhIG5vIHBlcmRlciBvYnNlcnZhY2lvbmVzIHZhbGlvc2FzLCBoZW1vcyBhcGxpY2FkbyB1bmEgZXN0cmF0ZWdpYSBkZSBpbXB1dGFjacOzbiBkaWZlcmVudGUsIHNlZ8O6biBjYWRhIHZhcmlhYmxlOgoKLSAgICoqQWN0aXZpZGFkIChgcmV2aWV3c19wZXJfbW9udGhgKToqKiBMb3MgbnVsb3Mgc2UgaW1wdXRhbiBjb24gKiowKiosIGludGVycHJldGFuZG8gbGEgYXVzZW5jaWEgZGUgZGF0byBjb21vIGFjdGl2aWRhZCBudWxhLgoKLSAgICoqRXN0cnVjdHVyYWwgKGBiZWRyb29tc2ApOioqIFNlIGltcHV0YSBjb24gbGEgKiptZWRpYW5hKiosIGFsIHNlciB1bmEgbWVkaWRhIG3DoXMgcm9idXN0YSBmcmVudGUgYSB2YWxvcmVzIGF0w61waWNvcyBlbiB2YXJpYWJsZXMgZGUgY29udGVvIGVudGVyby4KCi0gICAqKkNhbGlkYWQgKGByZXZpZXdfc2NvcmVzYCk6KiogU2UgaW1wdXRhIGNvbiBsYSAqKm1lZGlhKiogZ2xvYmFsIHBhcmEgbWFudGVuZXIgbGEgbmV1dHJhbGlkYWQgZGUgbGEgb2JzZXJ2YWNpw7NuIHNpbiBwZW5hbGl6YXJsYSBhcnRpZmljaWFsbWVudGUuCgotICAgKipOb2NoZXMgbWluaW1hcyAoYG1pbmltdW1fbmlnaHRzYCoqKTogU2UgaW1wdXRhbiBsb3MgbnVsb3MgY29uICoqMSoqLCBhc3VtaWVuZG8gbGEgZXN0YW5jaWEgbcOtbmltYSBlc3TDoW5kYXIuCgoqKjUuIFNlbGVjY2nDs24geSBSZW5vbWJyYWRvIEZpbmFsKiogOiBoZW1vcyBnZW5lcmFkbyBlbCBkYXRhc2V0IGBkZl9maW5hbGAgZGVzY2FydGFuZG8gbGFzIHZhcmlhYmxlcyBpbnRlcm1lZGlhcyBzdWNpYXMgKGNvbW8gYGJhdGhyb29tc190ZXh0YCBvIGVsIGBwcmljZWAgb3JpZ2luYWwpIHkgcmV0ZW5pZW5kbyDDum5pY2FtZW50ZSBsYXMgKioxOSB2YXJpYWJsZXMgbGltcGlhcyB5IHRyYW5zZm9ybWFkYXMqKiBxdWUgbmVjZXNpdGFyZW1vcyBwYXJhIHRyYWJhamFyIGxvcyB0cmVzIG1vZGVsb3MgZGVsIHByb3llY3RvLgoKYGBge3J9CmhlYWQoZGZfZmluYWwpCmBgYAoKIyMgMi4zLiBGaWx0cmFkbyBkZSBJbmNvbnNpc3RlbmNpYXMKCmBgYHtyfQojIC0tLSAyLjMuIEZJTFRSQURPIERFIElOQ09OU0lTVEVOQ0lBUyBZIE9VVExJRVJTIC0tLQojIFBhcnRpbW9zIGRlIGRmX2ZpbmFsIHF1ZSB5YSB0aWVuZSBsYXMgdmFyaWFibGVzIGxpbXBpYXMgKGJhw7FvcywgYW1lbml0aWVzLCBldGMuKQoKZGZfZWRhIDwtIGRmX2ZpbmFsICU+JQogICMgQS4gRmlsdHJvIGRlIFByZWNpbzogQWp1c3RhbW9zIGEgdHUgcmFuZ28gKDEw4oKsIC0gMS4wMDDigqwpCiAgZmlsdGVyKHByaWNlID4gMTAgJiBwcmljZSA8IDEwMDApICU+JQogIAogICMgQi4gRmlsdHJvIEdlb2VzcGFjaWFsOiBTZWd1cmlkYWQgcG9yIHNpIGZhbGzDsyBhbGdvCiAgZmlsdGVyKCFpcy5uYShsYXRpdHVkZSkgJiAhaXMubmEobG9uZ2l0dWRlKSkgJT4lCiAgCiAgIyBDLiBGaWx0cm8gZGUgRXN0YW5jaWFzOiBFbGltaW5hbW9zIGFscXVpbGVyZXMgZGUgYcOxbyBjb21wbGV0byAocG9zaWJsZXMgZXJyb3JlcykKICBmaWx0ZXIobWluaW11bV9uaWdodHMgPCAzNjUpCgojIFZlciBjdcOhbnRhcyBmaWxhcyBub3MgcXVlZGFuIHZzIGxhcyBvcmlnaW5hbGVzCnByaW50KHBhc3RlKCJPYnNlcnZhY2lvbmVzIG9yaWdpbmFsZXM6IiwgbnJvdyhkZl9maW5hbCkpKQpwcmludChwYXN0ZSgiT2JzZXJ2YWNpb25lcyBwYXJhIGFuw6FsaXNpcyAoZGZfZWRhKToiLCBucm93KGRmX2VkYSkpKQpgYGAKCiMgMy4gQW7DoWxpc2lzIEV4cGxvcmF0b3JpbyBkZSBEYXRvcwoKIyMgMy4xLiBBbsOhbGlzaXMgVW5pdmFyaWFudGUKCiMjIyAzLjEuMSBEaXN0cmlidWNpw7NuIGRlbCBwcmVjaW8KCmBgYHtyfQojIDMuMS4xIERpc3RyaWJ1Y2nDs24gZGVsIFByZWNpbwpnZ3Bsb3QoZGZfZWRhLCBhZXMoeCA9IHByaWNlKSkgKwogIGdlb21faGlzdG9ncmFtKGJpbndpZHRoID0gMTAsIGZpbGwgPSAic3RlZWxibHVlIiwgY29sb3IgPSAid2hpdGUiKSArCiAgdGhlbWVfbWluaW1hbCgpICsKICBsYWJzKHRpdGxlID0gIkRpc3RyaWJ1Y2nDs24gZGUgUHJlY2lvcyAoRmlsdHJhZG8gMTDigqwgLSAxMDAw4oKsKSIsIAogICAgICAgc3VidGl0bGUgPSAiTGEgbWF5b3LDrWEgZGVsIG1lcmNhZG8gc2UgY29uY2VudHJhIGJham8gbG9zIDIwMOKCrCIsCiAgICAgICB4ID0gIlByZWNpbyAo4oKsKSIsIHkgPSAiRnJlY3VlbmNpYSIpCgpgYGAKCiMjIyAzLjEuMiBDb250ZW8gZGVsIHRpcG8gZGUgaGFiaXRhY2lvbgoKYGBge3J9CiMgMy4xLjIgQ29udGVvIHBvciBUaXBvIGRlIEhhYml0YWNpw7NuCmdncGxvdChkZl9lZGEsIGFlcyh4ID0gcm9vbV90eXBlLCBmaWxsID0gcm9vbV90eXBlKSkgKwogIGdlb21fYmFyKCkgKwogIGdlb21fdGV4dChzdGF0PSdjb3VudCcsIGFlcyhsYWJlbD0uLmNvdW50Li4pLCB2anVzdD0tMC41KSArCiAgdGhlbWVfbWluaW1hbCgpICsKICBzY2FsZV9maWxsX2JyZXdlcihwYWxldHRlID0gIlBhc3RlbDEiKSArCiAgbGFicyh0aXRsZSA9ICJPZmVydGEgcG9yIFRpcG8gZGUgQWxvamFtaWVudG8iKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKQpgYGAKCiMjIyAzLjEuMyBCb3hwbG90IGRlIG5vY2hlcyBtaW5pbWFzCgpgYGB7cn0KIyAzLjEuMyAoTlVFVk8pIERpc3RyaWJ1Y2nDs24gZGUgU2VydmljaW9zIChBbWVuaXRpZXMgQ291bnQpCiMgSW1wb3J0YW50ZSBwYXJhIGp1c3RpZmljYXIgcXVlIGhheSBwaXNvcyAicGVsYWRvcyIgeSBwaXNvcyAiZXF1aXBhZG9zIgpnZ3Bsb3QoZGZfZWRhLCBhZXMoeCA9IGFtZW5pdGllc19jb3VudCkpICsKICBnZW9tX2hpc3RvZ3JhbShiaW53aWR0aCA9IDIsIGZpbGwgPSAicHVycGxlIiwgYWxwaGEgPSAwLjcpICsKICB0aGVtZV9taW5pbWFsKCkgKwogIGxhYnModGl0bGUgPSAiRGlzdHJpYnVjacOzbiBkZSBFcXVpcGFtaWVudG8gKEFtZW5pdGllcykiLCAKICAgICAgIHggPSAiQ2FudGlkYWQgZGUgU2VydmljaW9zIiwgeSA9ICJOwrogQWxvamFtaWVudG9zIikKCgpgYGAKCiMjIDMuMi4gQW7DoWxpc2lzIEJpdmFyaWFudGUKCiMjIyAzLjIuMSBNYXRyaXogZGUgQ29ycmVsYWNpw7NuCgpBcXXDrSBkZXRlY3RhbW9zIGxhIE11bHRpY29saW5lYWxpZGFkCgpgYGB7cn0KIyAzLjIuMSBNYXRyaXogZGUgQ29ycmVsYWNpw7NuIChVc2FuZG8gbGFzIG51ZXZhcyB2YXJpYWJsZXMgbnVtw6lyaWNhcykKbGlicmFyeShjb3JycGxvdCkKCiMgU2VsZWNjaW9uYW1vcyBsYXMgbnVtw6lyaWNhcyBjbGF2ZSBkZWwgbnVldm8gZGF0YXNldApudW1zIDwtIGRmX2VkYSAlPiUgCiAgc2VsZWN0KHByaWNlLCBhY2NvbW1vZGF0ZXMsIGJlZHJvb21zLCBiYXRocm9vbXMsICMgVHUgdmFyaWFibGUgbGltcGlhCiAgICAgICAgIGFtZW5pdGllc19jb3VudCwgbWluaW11bV9uaWdodHMsICAgICAgICAgICMgVHVzIHZhcmlhYmxlcyBudWV2YXMKICAgICAgICAgbnVtYmVyX29mX3Jldmlld3MsIHJldmlld3NfcGVyX21vbnRoLCAKICAgICAgICAgcmV2aWV3X3Njb3Jlc19yYXRpbmcpICU+JQogIG5hLm9taXQoKQoKTSA8LSBjb3IobnVtcykKCmNvcnJwbG90KE0sIG1ldGhvZCA9ICJjb2xvciIsIHR5cGUgPSAidXBwZXIiLCAKICAgICAgICAgYWRkQ29lZi5jb2wgPSAiYmxhY2siLCBudW1iZXIuY2V4ID0gMC43LCAjIFRhbWHDsW8gbGV0cmEgbsO6bWVyb3MKICAgICAgICAgdGwuY29sID0gImJsYWNrIiwgdGwuc3J0ID0gNDUsIAogICAgICAgICBkaWFnID0gRkFMU0UsCiAgICAgICAgIHRpdGxlID0gIk1hdHJpeiBkZSBDb3JyZWxhY2nDs24gKFZhcmlhYmxlcyBGaW5hbGVzKSIsIAogICAgICAgICBtYXIgPSBjKDAsMCwxLDApKSAjIEFqdXN0ZSBkZSBtYXJnZW4gcGFyYSBlbCB0w610dWxvCgoKYGBgCgojIyMgMy4yLjIgSW1wYWN0byBkZWwgQmHDsW8gQ29tcGFydGlkbwoKYGBge3J9CiMgMy4yLjIgSW1wYWN0byBkZWwgQmHDsW8gQ29tcGFydGlkbwojIEVzdG8gdmFsaWRhIHZpc3VhbG1lbnRlIHNpICdiYXRoX3NoYXJlZCcgYWZlY3RhIGFsIHByZWNpbwpnZ3Bsb3QoZGZfZWRhLCBhZXMoeCA9IGFzLmZhY3RvcihiYXRoX3NoYXJlZCksIHkgPSBwcmljZSwgZmlsbCA9IGFzLmZhY3RvcihiYXRoX3NoYXJlZCkpKSArCiAgZ2VvbV9ib3hwbG90KGFscGhhID0gMC43KSArCiAgc2NhbGVfeF9kaXNjcmV0ZShsYWJlbHMgPSBjKCIwIiA9ICJQcml2YWRvIiwgIjEiID0gIkNvbXBhcnRpZG8iKSkgKwogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGMoIiMwMEFGQkIiLCAiI0ZDNEUwNyIpKSArCiAgdGhlbWVfbWluaW1hbCgpICsKICB5bGltKDAsIDMwMCkgKyAjIFpvb20gcGFyYSB2ZXIgbWVqb3IgbGFzIGNhamFzIChpZ25vcmFuZG8gbWFuc2lvbmVzKQogIGxhYnModGl0bGUgPSAiSW1wYWN0byBkZWwgQmHDsW8gQ29tcGFydGlkbyBlbiBlbCBQcmVjaW8iLCAKICAgICAgIHggPSAiVGlwbyBkZSBCYcOxbyIsIHkgPSAiUHJlY2lvICjigqwpIiwgZmlsbCA9ICIiKQoKCmBgYAoKIyMjIDMuMi4zIFNjYXR0ZXJwbG90IFByaWNlIHZzIFJldmlld3MKCmBgYHtyfQojIDMuMi4zIFByZWNpbyB2cyBSZXZpZXdzIChSb3RhY2nDs24pCmdncGxvdChkZl9lZGEsIGFlcyh4ID0gcmV2aWV3c19wZXJfbW9udGgsIHkgPSBwcmljZSkpICsKICBnZW9tX3BvaW50KGFscGhhID0gMC4zLCBjb2xvciA9ICJkYXJrYmx1ZSIpICsKICBnZW9tX3Ntb290aChtZXRob2QgPSAibG0iLCBjb2xvciA9ICJyZWQiLCBzZSA9IEZBTFNFKSArCiAgdGhlbWVfbWluaW1hbCgpICsKICBsYWJzKHRpdGxlID0gIlJlbGFjacOzbiBQcmVjaW8gdnMgUm90YWNpw7NuIiwgeCA9ICJSZXZpZXdzL21lcyIsIHkgPSAiUHJlY2lvIikKYGBgCgojIyAzLjMuIEFuw6FsaXNpcyBHZW9lc3BhY2lhbAoKIyMjIDMuMy4xIE1hcGEgZGUgUHJlY2lvcyBjb24gbGEgR2lyYWxkYQoKYGBge3J9CiMgMy4zLjEgTWFwYSBkZSBQcmVjaW9zIGNvbiBsYSBHaXJhbGRhCmdncGxvdChkZl9lZGEsIGFlcyh4ID0gbG9uZ2l0dWRlLCB5ID0gbGF0aXR1ZGUsIGNvbG9yID0gcHJpY2UpKSArCiAgZ2VvbV9wb2ludChzaXplID0gMC44LCBhbHBoYSA9IDAuNikgKwogIAogICMgQcORQURJUiBMQSBHSVJBTERBIC0gUHVudG8gZGUgcmVmZXJlbmNpYQogIGFubm90YXRlKCJwb2ludCIsIHggPSAtNS45OTIzOCwgeSA9IDM3LjM4NjE0LCBjb2xvciA9ICJ5ZWxsb3ciLCBzaXplID0gMywgc2hhcGUgPSAxNykgKyAKICBhbm5vdGF0ZSgidGV4dCIsIHggPSAtNS45OTIzOCwgeSA9IDM3LjM4NjE0LCBsYWJlbCA9ICJHaXJhbGRhIiwgdmp1c3QgPSAtMS41LCBjb2xvciA9ICJ5ZWxsb3ciLCBmb250ZmFjZSA9ICJib2xkIiwgc2l6ZT0zKSArCiAgCiAgc2NhbGVfY29sb3JfdmlyaWRpc19jKG9wdGlvbiA9ICJtYWdtYSIsIGRpcmVjdGlvbiA9IC0xLCBsaW1pdHMgPSBjKDAsIDMwMCksIG9vYiA9IHNjYWxlczo6c3F1aXNoKSArIAogICMgbGltaXRzPWMoMCwzMDApIGhhY2UgcXVlIGxvcyBjb2xvcmVzIHNlIGNlbnRyZW4gZW4gcGlzb3Mgbm9ybWFsZXMsIG9vYj1zcXVpc2ggcGludGEgbG9zIGNhcm9zIGRlbCBjb2xvciBtw6F4aW1vCiAgCiAgY29vcmRfcXVpY2ttYXAoKSArCiAgdGhlbWVfdm9pZCgpICsKICBsYWJzKHRpdGxlID0gIk1hcGEgZGUgUHJlY2lvcyBTZXZpbGxhIENlbnRybyIsIGNvbG9yID0gIlByZWNpbyIpCmBgYAoKIyA0LiBBbsOhbGlzaXMgR3J1cGFsCgojIyA0LjEuIFNlZ21lbnRhY2nDs24gZGVsIE1lcmNhZG8KCiMjIDQuMi4gRGV0ZXJtaW5hbnRlcyBkZWwgUHJlY2lvCgojIyA0LjMuIEVsICJFZmVjdG8gR2lyYWxkYSIK